package org.softee.functional.samples;

import static java.lang.String.CASE_INSENSITIVE_ORDER;
import static org.softee.functional.FunctionalIterables.from;
import static org.softee.functional.functions.Mappers.stringLength;
import static org.softee.functional.functions.Predicates.compose;
import static org.softee.functional.functions.Predicates.containsRegex;
import static org.softee.functional.functions.Predicates.isEqualTo;
import static org.softee.functional.functions.Predicates.isGreaterThan;
import static org.softee.functional.functions.Predicates.matches;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;

import org.junit.Test;
import org.softee.functional.functions.BinaryOperators;
import org.softee.functional.functions.Block;
import org.softee.functional.functions.Mapper;
import org.softee.functional.functions.Predicate;
import org.softee.functional.functions.Predicates;

public class Samples {
    private static final Collection<String> WORDS =
            words("Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
                    + "Aenean sit amet sem ante. Donec sem ligula, posuere id facilisis vitae.");

    @Test
    public void filterFourLetterWordsSortUnique() {
        Predicate<String> fourLetterPredicate = Predicates.compose(
                stringLength(),
                isEqualTo(4));

        System.out.println("Words: " + WORDS);

        Iterable<String> values = from(WORDS)
                .filter(fourLetterPredicate)
                .sorted(CASE_INSENSITIVE_ORDER)
                .uniqueElements();

        System.out.println("Unique sorted four letter words: " + values);
    }

    @Test
    public void sortWordsByLength() {
        System.out.println("Words: " + WORDS);

        Comparator<String> stringLengthOrder = new Comparator<String>() {
            @Override
            public int compare(String one, String other) {
                return one.length() - other.length();
            }
        };

        Iterable<String> result = from(WORDS).sorted(stringLengthOrder);

        System.out.println("Sorted words by length: " + result);
    }

    @Test
    public void UniqueSortedShortWords() {
        System.out.println("Words: " + WORDS);

        Iterable<String> uniqueSortedWords = from(WORDS)
                .filter(compose(
                        stringLength(),
                        Predicates.isLessThan(5)))
                .sorted(CASE_INSENSITIVE_ORDER)
                .removeDuplicates(); // since the words are sorted, use duplicate removal, as it is quicker

        System.out.println("Unique sorted words: " + uniqueSortedWords);
    }

    @Test
    public void filterLongWordsSortReverse() {

        Predicate<String> longWordPredicate = Predicates.compose(
                stringLength(),
                isGreaterThan(4));

        System.out.println("Words: " + WORDS);

        Iterable<String> values = from(WORDS)
                .filter(longWordPredicate)
                .sorted(Collections.reverseOrder(CASE_INSENSITIVE_ORDER));

        System.out.println("Long sorted words: " + values);
    }

    @Test
    public void findWordsSortReverse() {
        Mapper<String, String> stringReverse = new Mapper<String, String>() {
            @Override
            public String apply(String input) {
                StringBuilder sb = new StringBuilder(input.length());
                int i = input.length();
                while (i > 0) {
                    sb.append(input.charAt(--i));
                }
                return sb.toString();
            }
        };

        System.out.println("Words: " + WORDS);

        Iterable<String> values = from(WORDS)
                .filter(matches("[aA].*"))
                .sorted(CASE_INSENSITIVE_ORDER)
                .map(stringReverse);

        System.out.println(values);
    }

    @Test
    public void capitalizedWordsAZ() {
        System.out.println("Words: " + WORDS);

        Iterable<String> values = from(WORDS).filter(containsRegex("^[A-Z]"));

        System.out.println("Capitalized words: " + values);
    }

    @Test
    public void LengthOfLongestWord1() {
        System.out.println("Words: " + WORDS);

        Integer length = from(WORDS)
                // TODO Ugly generics cast
                .mapReduce(stringLength(), 0, BinaryOperators.<Integer>max());

        System.out.println("Length of longest word: " + length);
    }

    @Test
    public void LengthOfLongestWord2() {
        System.out.println("Words: " + WORDS);

        Integer length = from(WORDS)
                .map(stringLength())
                // TODO Ugly generics cast
                .reduce(0, BinaryOperators.<Integer>max());

        System.out.println("Length of longest word: " + length);
    }

    @Test
    public void totalLengthOfCapitalizedWords1() {
        System.out.println("Words: " + WORDS);

        Integer length = from(WORDS)
                .filter(containsRegex("^\\p{Lu}"))
                .mapReduce(stringLength(), 0, BinaryOperators.integerAdd());

        System.out.println("Length of capitalized words: " + length);
    }

    @Test
    public void totalLengthOfCapitalizedWords2() {
        System.out.println("Words: " + WORDS);

        Block<String> printingBlock = new Block<String>() {
            @Override
            public void apply(String data) {
                System.out.println(">" + data);
            }
        };

        Integer length = from(WORDS)
                .filter(containsRegex("^\\p{Lu}"))
                .forEach(printingBlock)
                .mapReduce(stringLength(), 0, BinaryOperators.integerAdd());

        System.out.println("Length of capitalized words: " + length);
    }

    @Test
    public void totalLengthOfCapitalizedWords3() {
        Predicate<String> isCapitalized = new Predicate<String>() {
            @Override
            public boolean apply(String input) {
                return input != null
                        && !input.isEmpty()
                        && Character.isUpperCase(input.charAt(0));
            }
        };

        System.out.println("Words: " + WORDS);

        Integer length = from(WORDS)
                .filter(isCapitalized)
                .mapReduce(stringLength(), 0, BinaryOperators.integerAdd());

        System.out.println("Length of capitalized words: " + length);
    }

    private static Collection<String> words(String words) {
        return Arrays.asList(words.split("\\W+"));
    }
}
